﻿
using System;
using System.Collections.Generic;
using System.Linq;
using Test30.Util;

namespace Test30.Engine
{
    public class Game
    {
        public const int MaxDepth = 100;
        public readonly GameEvent<object, ValueEventArgs<int>> FloorChanged = new GameEvent<object, ValueEventArgs<int>>();
        public readonly GameEvent<Thing, EventArgs> ThingNoticed = new GameEvent<Thing, EventArgs>();
        public Content Content { get; }
        public Dungeon Dungeon { get; }
        public Hero Hero { get; }
        public EffectCollection Effects { get; }
        public Entity ActingEntity { get; private set; }
        public int Depth { get; private set; }
        public Game(Hero hero,Content content)
        {
            Effects = new EffectCollection();
            Content = content;
            Hero = hero;
            Dungeon = new Dungeon(this);
            Depth = 0;

            Hero.LightRadiusChanged += Hero_LightRadiusChanged;
            ChangeFloor(1);
            _state = GameState.Playing;
        }

        public void CanelAction()
        {
            if (_cancelEntity != null)
            {
                _cancelEntity.Behavior.Cancel();
                _cancelEntity = null;
            }
        }

        public void ChangeFloor(int change)
        {
            SetFloor(Depth + change);
        }
        public void Lose()
        {
            _state = GameState.Over;
        }
        public GameResult Process()
        {
            // create the enumerator
            if (_processEnumerator == null)
            {
                _processEnumerator = CreateProcessEnumerable().GetEnumerator();
            }

            // advance one step
            if (!_processEnumerator.MoveNext())
            {
                // no more results to enumerate, game must have ended
                return new GameResult(GameResultFlags.GameOver | GameResultFlags.NeedsPause);
            }

            return _processEnumerator.Current;
        }

        public void SetFloor(int floor)
        {
            int change = floor - Depth;
            Depth = floor;
            Dungeon.Generate((change > 0), Depth);
            Hero.NoteDepth(floor);

            FloorChanged.Raise(null, new ValueEventArgs<int>(floor));
            Dungeon.DirtyVisibility();
            Dungeon.RefreshView(this);
        }

        private IEnumerable<GameResult> ProcessAction(Action theAction)
        {
            Queue<Action> actions = new Queue<Action>();
            actions.Enqueue(theAction);

            while (actions.Count > 0)
            {
                // clear the effects
                Effects.Clear();

                Action action = actions.Peek();

                // track who owns this sequence of actions
                ActingEntity = action.Entity;

                ActionResult result = action.Process(Effects, actions);

                // cascade through the alternates until we hit the "real" action to process
                while (result.Alternate != null)
                {
                    result = result.Alternate.Process(Effects, actions);
                }

                // remove it if complete
                if (result.IsDone)
                {
                    actions.Dequeue();
                }

                // run the post step
                if (result.Success)
                {
                    action.AfterSuccess();
                }

                ActingEntity = null;

                // make sure the lighting and visibility are up to date
                Dungeon.RefreshView(this);

                // assume we do not need to cancel
                _cancelEntity = null;

                if (result.NeedsPause)
                {
                    // return and pause
                    yield return new GameResult(GameResultFlags.NeedsPause);
                }
                else if (result.NeedsCheckForCancel)
                {
                    // return and check for a cancel
                    _cancelEntity = action.Entity;
                    yield return new GameResult(GameResultFlags.CheckForCancel);
                }
                else if (Effects.Count > 0)
                {
                    // show the effects if there are any
                    yield return new GameResult(GameResultFlags.NeedsPause);
                }

                // if the game ended, just continue to return that infinitely
                while (_state == GameState.Over)
                {
                    yield return new GameResult(GameResultFlags.GameOver | GameResultFlags.NeedsPause);

                    Effects.Clear();
                }
            }
        }

        private IEnumerable<GameResult> CreateProcessEnumerable()
        {
            // used to process the items at slow speed
            Energy itemEnergy = new Energy(Energy.MinSpeed);

            while (_state == GameState.Playing)
            {
                for (int entityIndex = 0; entityIndex < Dungeon.Entities.Count; entityIndex++)
                {
                    Entity entity = Dungeon.Entities[entityIndex];

                    // make sure the entity has enough energy to move
                    // once it does, keep trying until it makes a successful action
                    while (entity.Energy.HasEnergy)
                    {
                        // bail if we need to wait for the ui to provide an action
                        while (entity.Behavior.NeedsUserInput)
                        {
                            Effects.Clear();
                            yield return new GameResult(GameResultFlags.NeedsUserInput, entity);
                        }

                        // get the entity's actions
                        foreach (Action action in entity.TakeTurn())
                        {
                            // process it and everything it leads to
                            foreach (GameResult result in ProcessAction(action))
                            {
                                yield return result;
                            }
                        }
                    }

                    // entity was killed, so it will be removed from the collection and the next
                    // one shifted up.
                    if (!(entity is Hero) && !entity.IsAlive)
                    {
                        entityIndex--;
                    }
                }

                // process all of the items
                if (itemEnergy.HasEnergy)
                {
                    foreach (Item item in Dungeon.Items.Concat(Hero.Inventory).Concat(Hero.Equipment))
                    {
                        // skip null items because the hero's inventory and equipment can contain empty slots
                        Action itemAction = item?.TakeTurn(this);

                        if (itemAction != null)
                        {
                            // process it and everything it leads to
                            foreach (GameResult result in ProcessAction(itemAction))
                            {
                                yield return result;
                            }
                        }
                    }

                    itemEnergy.Spend();
                }

                // a turn has completed, so give everything energy
                Dungeon.Entities.ForEach(entity => entity.Energy.Gain());
                itemEnergy.Gain();
            }
        }

        private void Hero_LightRadiusChanged(object sender, EventArgs e)
        {
            Dungeon.DirtyLighting();
        }
        private IEnumerator<GameResult> _processEnumerator;
        private GameState _state;
        private Entity _cancelEntity;
    }
}
